package ro.myApp.commons.service;

import java.security.Principal;

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;

import ro.myApp.commons.basic.PersistentEntity;
import ro.myApp.commons.service.exceptions.EntityDeleteRollbackException;
import ro.myApp.commons.utils.CustomAssertion;

public abstract class EntityActionServiceImpl<E extends PersistentEntity> 
	implements EntityActionService<E> {

	@Resource
	protected SessionContext context;
	
	protected abstract EntityManager getEm();
	
	@Override
	@TransactionAttribute(TransactionAttributeType.NEVER)
	public E createEntity(Class<E> clazz, String username) {
		if (clazz == null)
			throw new IllegalArgumentException("class parameter is mandatory");
		try {
			E result = clazz.newInstance();
//			Principal p = context.getCallerPrincipal();
			CustomAssertion.assertNotNull(result, "error on entity instantiation");
			if (null==result.getCreatedByUsername())
				result.setCreatedByUsername(username);
			return result;
		} catch (InstantiationException e) {
			e.printStackTrace(); 
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	@Override
	@TransactionAttribute(TransactionAttributeType.NEVER)
	public <F extends PersistentEntity> F createOtherEntity(Class<F> clazz, String username) {
		if (clazz == null)
			throw new IllegalArgumentException("class parameter is mandatory");
		try {
			F result = clazz.newInstance();
//			Principal p = context.getCallerPrincipal();
			CustomAssertion.assertNotNull(result, "error on entity instantiation");
			if (null==result.getCreatedByUsername())
				result.setCreatedByUsername(username);
			return result;
		} catch (InstantiationException e) {
			e.printStackTrace(); 
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}


	@Override
	public E updateEntity(E entity) {entity.setCreatedByUsername("xxx");
		if (null==entity)
			throw new IllegalArgumentException("entity to update can not be null");
		if (null==entity.getCreatedByUsername() || entity.getCreatedByUsername().isEmpty())
			throw new IllegalStateException("entity reference was not obtained in desired way: " +
					"createEntity method, clone of a valid entity instcance or query from database");
		try{
			if (null!=entity.getId()){//is update
				Principal p = context.getCallerPrincipal();
				entity.setUpdatedByUsername(p.getName());
			}
			return this.getEm().merge(entity);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public <F extends PersistentEntity> F updateOtherEntity(F entity) {
		if (null==entity)
			throw new IllegalArgumentException("entity to update can not be null");
		if (null==entity.getCreatedByUsername() || entity.getCreatedByUsername().isEmpty())
			throw new IllegalStateException("entity reference was not obtained in desired way: " +
					"createEntity method, clone of a valid entity instcance or query from database");
		try{
			if (null!=entity.getId()){//is update
				Principal p = context.getCallerPrincipal();
				entity.setUpdatedByUsername(p.getName());
			}
			return this.getEm().merge(entity);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public boolean deleteEntity(E entity) {
		if (null==entity)
			throw new IllegalArgumentException("entity to delete can not be null");
		try{
			this.tryDelete((Class<E>) entity.getClass(), entity.getId());
		}catch (EntityDeleteRollbackException e) {
			//tested if delete action wouldn't violate constraints and if passed save it as deleted
			entity.setDeleted(true);
			this.updateEntity(entity);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
	
	@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
	protected void tryDelete (Class<E> clazz, Long id) throws EntityDeleteRollbackException{
		try{
			this.getEm().remove(this.getEm().find(clazz,id));
			this.getEm().flush();
		}catch (Throwable e) {
			e.printStackTrace();
		}
//		check if delete doesn't trigger any database error and force rollback 
//		so that the entity is not removed from database
		throw new EntityDeleteRollbackException();
	}
	
	@Override
	public boolean deleteEntityPermanently(E entity) {
		if (null==entity)
			throw new IllegalArgumentException("entity to delete can not be null");
		if (null==entity.getId())
			return true;
		try{
			this.getEm().remove(entity);
			return true;
		}catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public void flush() {
		this.getEm().flush();
	}

}
